<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>4.3.2-设计一个完善的响应系统</title>
</head>

<body>
    <div id="container"></div>
</body>
<script>
    /**
     * 在副作用函数与被操作的字段之间建立联系
     * - 重新设计“桶”的数据结构，而不能简单地使用一个 Set 类型的数据作为“桶”
     * 
     * 分别使用了WeakMap、Map 和 Set
     * - WeakMap 由 target => Map 构成
     * - Map 由 key => Set 构成
     * 
     * 为什么要使用WeakMap
     * - WeakMap 对 key 是弱引用，不影响垃圾回收器的工作
     *     一旦 key 被垃圾回收器回收，那么对应的键和值就访问不到了
     * - 如果使用 Map 来代替 WeakMap，
     *    那么即使用户侧的代码对 target 没有任何引用，
     *    这个 target 也不会被回收，最终可能导致内存溢出
     * */ 

    // 存储副作用函数的桶
    const bucket = new WeakMap() // 新增 WeakMap
    // 原始数据
    const data = { text: 'hello world' }
    // 对原始数据的代理
    const obj = new Proxy(data, {
        // 拦截读取操作
        get(target, key) {  // 新增 （完全修改）
            // 没有 activeEffect，直接 return
            if (!activeEffect) return target[key]
            // 根据 target 从“桶”中取得 depsMap，它也是一个 Map 类型：key --> effects
            let depsMap = bucket.get(target)
            // 如果不存在 depsMap，那么新建一个 Map 并与 target 关联
            if (!depsMap) {
                bucket.set(target, (depsMap = new Map()))
            }
            // 再根据 key 从 depsMap 中取得 deps，它是一个 Set 类型，
            // 里面存储着所有与当前 key 相关联的副作用函数：effects
            let deps = depsMap.get(key)
            // 如果 deps 不存在，同样新建一个 Set 并与 key 关联
            if (!deps) { 
                // deps 使用 Set 类型，是为了->防止多次访问同一个key值时，副作用函数重复添加,
                // Set 类型的特点是，如果添加的元素已经存在，那么不会重复添加
                depsMap.set(key, (deps = new Set()))
            }
            // 最后将当前激活的副作用函数添加到“桶”里
            deps.add(activeEffect)

            // 返回属性值
            return target[key]
        },
        // 拦截设置操作
        set(target, key, newVal) {  // 新增 （完全修改）
            // 设置属性值
            target[key] = newVal
            // 根据 target 从桶中取得 depsMap，它是 key --> effects
            const depsMap = bucket.get(target)
            if (!depsMap) return
            // 根据 key 取得所有副作用函数 effects
            const effects = depsMap.get(key)
            // 执行副作用函数
            effects && effects.forEach(fn => fn())
        }
    })
    
    // 用一个全局变量存储被注册的副作用函数
    let activeEffect
    // effect 函数用于注册副作用函数
    function effect(fn) {
        // 当调用 effect 注册副作用函数时，将副作用函数 fn 赋值给 activeEffect
        activeEffect = fn
        // 执行副作用函数
        fn()
    }
    // 如下所示的方式使用 effect 函数
    effect(
        // 一个匿名的副作用函数
        () => {
            // 会触发响应式数据 obj.text 的读取操作
            // 进而触发代理对象 Proxy 的 get 拦截函数：
            console.log('effect run') // 会打印 1 次 !!
            document.body.innerText = obj.text
        }
    )

    setTimeout(() => {
        // 副作用函数中并没有读取 notExist 属性的值
        obj.notExist = 'hello vue3'
        console.log('obj.notExist', obj.notExist)
    }, 1000)
</script>

</html>